feat: add local PDF OCR with PDFOxide#658
Conversation
Deploying maple with
|
| Latest commit: |
3097225
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://fba80549.maple-ca8.pages.dev |
| Branch Preview URL: | https://codex-maple-pdf-oxide-ocr-ma.maple-ca8.pages.dev |
📝 WalkthroughWalkthroughMaple adds PDFOxide extraction with local PaddleOCR, pinned model caching, shared ONNX Runtime 1.23.2 provisioning, Android artifact verification, frontend upload lifecycle guards, and updated desktop/iOS build configuration and documentation. ChangesDocument processing pipeline
Shared ONNX Runtime provisioning
Operational documentation
Estimated code review effort: 5 (Critical) | ~100 minutes Sequence Diagram(s)sequenceDiagram
participant UnifiedChat
participant Tauri
participant PDFOxide
participant OCRModelCache
participant OcrEngine
UnifiedChat->>Tauri: submit PDF attachment
Tauri->>PDFOxide: classify pages and extract native text
PDFOxide-->>Tauri: native text and OCR requirements
Tauri->>OCRModelCache: verify or download pinned models
OCRModelCache-->>Tauri: verified model files
Tauri->>OcrEngine: process bounded rendered pages
OcrEngine-->>Tauri: OCR spans
Tauri-->>UnifiedChat: merged extracted text
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/src/components/UnifiedChat.tsx (2)
2359-2438: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCancel parsing when the document is removed.
clearAllAttachmentsinvalidates pending uploads, butremoveDocumentdoes not. If a user removes an existing attachment while a replacement PDF is processing, the pending result can still be attached afterward. Invalidate the generation and clear the processing state fromremoveDocument.Proposed fix
const removeDocument = useCallback(() => { + documentUploadGenerationRef.current += 1; + setIsProcessingDocument(false); setDocumentText(""); setDocumentName(""); }, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/UnifiedChat.tsx` around lines 2359 - 2438, Update removeDocument to increment documentUploadGenerationRef.current and clear the document-processing state via setIsProcessingDocument(false), matching clearAllAttachments. Ensure any pending replacement upload fails its generation check and cannot attach results after the document is removed.
2362-2428: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReport unsupported document selections.
When
getSupportedDocumentType()returnsnull, this handler clears the processing state without attaching anything or showing an error.acceptis only a picker hint, so reject this path explicitly.Proposed fix
} else if (documentType === "pdf") { setAttachmentError("PDF files can only be processed in the Maple app"); setTimeout(() => setAttachmentError(null), 5000); + } else { + setAttachmentError("Only PDF, TXT, and Markdown files are supported"); + setTimeout(() => setAttachmentError(null), 5000); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/UnifiedChat.tsx` around lines 2362 - 2428, Update the document upload handler around getSupportedDocumentType so a null unsupported-document result explicitly sets an attachment error and exits without attaching content, rather than silently clearing processing state. Preserve the existing text and PDF handling for supported document types, and use the established attachment-error cleanup behavior.
🧹 Nitpick comments (1)
frontend/src-tauri/src/pdf_extractor.rs (1)
408-419: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winOptional: precompute normalized extras to avoid O(n²) re-normalization.
extras.iter().any(|extra| normalize(extra) == normalized)re-runsnormalize(asplit_whitespace+join+to_lowercaseallocation) over every previously kept fragment on each iteration. For image-dense pages with many OCR spans this is quadratic. Store the normalized form alongside the kept fragment.♻️ Keep normalized alongside each extra
- let native_normalized = normalize(native_trimmed); - let mut extras: Vec<&str> = Vec::new(); + let native_normalized = normalize(native_trimmed); + let mut extras: Vec<&str> = Vec::new(); + let mut extras_normalized: Vec<String> = Vec::new(); for fragment in ocr_fragments { let fragment = fragment.trim(); let normalized = normalize(fragment); if normalized.is_empty() || native_normalized.contains(&normalized) - || extras.iter().any(|extra| normalize(extra) == normalized) + || extras_normalized.iter().any(|extra| *extra == normalized) { continue; } + extras_normalized.push(normalized); extras.push(fragment); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src-tauri/src/pdf_extractor.rs` around lines 408 - 419, Update the extras collection in the OCR fragment processing flow to store each kept fragment together with its normalized form, rather than retaining only &str values. Use the stored normalized values for duplicate checks and preserve the existing fragment text when pushing output, eliminating repeated normalize calls in extras.iter().any.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@frontend/src/components/UnifiedChat.tsx`:
- Around line 2359-2438: Update removeDocument to increment
documentUploadGenerationRef.current and clear the document-processing state via
setIsProcessingDocument(false), matching clearAllAttachments. Ensure any pending
replacement upload fails its generation check and cannot attach results after
the document is removed.
- Around line 2362-2428: Update the document upload handler around
getSupportedDocumentType so a null unsupported-document result explicitly sets
an attachment error and exits without attaching content, rather than silently
clearing processing state. Preserve the existing text and PDF handling for
supported document types, and use the established attachment-error cleanup
behavior.
---
Nitpick comments:
In `@frontend/src-tauri/src/pdf_extractor.rs`:
- Around line 408-419: Update the extras collection in the OCR fragment
processing flow to store each kept fragment together with its normalized form,
rather than retaining only &str values. Use the stored normalized values for
duplicate checks and preserve the existing fragment text when pushing output,
eliminating repeated normalize calls in extras.iter().any.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 76a489bf-e0c0-4aed-a9da-09540064a763
⛔ Files ignored due to path filters (1)
frontend/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
docs/pdf-ocr.mdfrontend/src-tauri/Cargo.tomlfrontend/src-tauri/src/lib.rsfrontend/src-tauri/src/pdf_extractor.rsfrontend/src-tauri/src/pdf_ocr.rsfrontend/src/components/UnifiedChat.tsxfrontend/src/utils/documentUpload.test.tsfrontend/src/utils/documentUpload.tsscripts/ci/ios-pr.shscripts/ci/ios-release.sh
5fc9ba1 to
a16cd86
Compare
Summary
pdf-extractwith PDFOxide 0.3.74 for native, scanned, and hybrid PDF handlingort = 2.0.0-rc.11/ ONNX Runtime 1.23.2 stack for PDF OCR and TTS; remove tract; statically link iOS and package dynamically loaded runtimes for macOS, Linux, Windows, and AndroidEnd-to-end proof
Validation
cargo fmt --check, changed-shell-scriptbash -n/ShellCheck,git diff --check, andnix flake check --no-buildpass after rebasing onto currentmasterDeliberately deferred
Summary by CodeRabbit
New Features
Improvements
Documentation